package com.peress.mycrew.rowing.data;

import android.location.Location;

import com.peress.mycrew.rowing.data.entries.Acceleration;

import java.util.LinkedList;

public final class RecordedPiece {

    private static final long LISTENER_UPDATE_RATE_MILLIS = 200;

    private static final int SAMPLE_TIME_FOR_CURRENT_SPEED_MILLIS = 2000;
    private static final float SPEED_SENSITIVITY = 0.004f;

    private static final int UPDATE_TOTAL_DISTANCE = 0x1;
    private static final int UPDATE_CURRENT_PACE   = 0x2;
    private static final int UPDATE_AVERAGE_PACE   = 0x4;

    private final long mCreatedTimestamp;
    private long mLastUpdateTimestamp;

    private PieceDataListener mDataListener;
    private int mUpdateFlag = 0;

    private int mTotalDistance = 0;
    private LinkedList<Location> mLocations = new LinkedList<>();
    private LinkedList<Acceleration> mAccelerations = new LinkedList<>();
    private LinkedList<Acceleration> mGravities = new LinkedList<>();

    private LinkedList<Location> mCurrentSpeedQueue = new LinkedList<>();
    private float mCurrentSpeedTotal;

    public RecordedPiece() {
        mCreatedTimestamp = System.currentTimeMillis();
        mLastUpdateTimestamp = 0;
        mCurrentSpeedTotal = 0;
    }

    public void setPieceDataListener(PieceDataListener listener) {
        mDataListener = listener;
    }

    public void unsetPieceDataListener() {
        mDataListener = null;
    }

    public void clear() {
        mTotalDistance = 0;
        mLocations.clear();
        mAccelerations.clear();
        mGravities.clear();
    }

    public void addLocation(Location location) {
        // Note location.getTime() returns nanoseconds?
        mLastUpdateTimestamp = System.currentTimeMillis();
        float oldCurrentSpeed = mCurrentSpeedTotal / mCurrentSpeedQueue.size();
        float oldAverageSpeed = getAverageSpeed();

        if (!mLocations.isEmpty()) {
            mTotalDistance += mLocations.getLast().distanceTo(location);
            mUpdateFlag |= UPDATE_TOTAL_DISTANCE;
        }

        mLocations.add(location);

        mCurrentSpeedQueue.addLast(location);
        mCurrentSpeedTotal += location.getSpeed();

        while (location.getTime() - mCurrentSpeedQueue.getFirst().getTime() >
                SAMPLE_TIME_FOR_CURRENT_SPEED_MILLIS) {
            mCurrentSpeedTotal -= mCurrentSpeedQueue.removeFirst().getSpeed();
        }

        if (mDataListener != null) {
            if (Math.abs(getCurrentSpeed() - oldCurrentSpeed) > SPEED_SENSITIVITY) {
                mUpdateFlag |= UPDATE_CURRENT_PACE;
            }
            if (Math.abs(getAverageSpeed() - oldAverageSpeed) > SPEED_SENSITIVITY) {
                mUpdateFlag |= UPDATE_AVERAGE_PACE;
            }
        }
        updateListener();
    }

    public void addGravityData(Acceleration gravity) {
        // Note gravity.getTime() returns nanoseconds?
        mLastUpdateTimestamp = System.currentTimeMillis();
        mGravities.add(gravity);
        updateListener();
    }

    public void addAcceleration(Acceleration acceleration) {
        // Note acceleration.getTime() returns nanoseconds?
        mLastUpdateTimestamp = System.currentTimeMillis();
        mAccelerations.add(acceleration);
        updateListener();
    }

    public int getTotalDistance() {
        return mTotalDistance;
    }

    public long getTotalTimeMillis() {
        return mLastUpdateTimestamp - mCreatedTimestamp;
    }

    private float getCurrentSpeed() {
        return mCurrentSpeedTotal / mCurrentSpeedQueue.size();
    }

    private float getAverageSpeed() {
        return (float) getTotalDistance() / (1000.0f * getTotalTimeMillis());
    }

    private void updateListener() {
        if (mDataListener == null) {
            mUpdateFlag = 0;
            return;
        }

        mDataListener.onTotalTimeChanged(getTotalTimeMillis());
        if ((mUpdateFlag & UPDATE_TOTAL_DISTANCE) != 0) {
            mDataListener.onTotalDistanceChanged(getTotalDistance());
        }
        if ((mUpdateFlag & UPDATE_CURRENT_PACE) != 0) {
            mDataListener.onCurrentPaceChanged(getCurrentSpeed());
        }
        if ((mUpdateFlag & UPDATE_AVERAGE_PACE) != 0) {
            mDataListener.onAveragePaceChanged(getAverageSpeed());
        }
        mUpdateFlag = 0;
    }
}
